﻿using System;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using my_proxy.Utils;
using System.Security.Cryptography;
using System.Configuration;


namespace my_proxy.RequestHandlers
{
    /// <summary>
    ///  Handle one request of the client from the server.
    /// </summary>
    public class OpenRequestHandler
    {
        protected HttpListenerContext Context;
        protected FileReader FileReader;
        protected object FileLock;
        protected Configuration Conf;
        protected List<string> blackList;

        /// <summary>
        /// Initialize the communication with the Remote Server - Open mode
        /// </summary>
        /// <param name="cf">The configuration file</param>
        /// <param name="context">The context of the request</param>
        /// <param name="fr">The file reader object</param>
        /// <param name="fileLock">A lock for synchonization</param>
        public OpenRequestHandler(Configuration cf,
            HttpListenerContext context, FileReader fr,object fileLock)
        {

            
            this.Context = context;
            this.RemoteUrl = this.Context.Request.RawUrl;
            this.FileReader = fr;
            this.FileLock = fileLock;
            this.Conf = cf;
            if (context.Request.RemoteEndPoint != null)
            {
                Log.Info(context.Request.RemoteEndPoint.Address + " " + context.Request.RawUrl);
            }
            else
            {
                string contextErrMsg = "Context error in: " + context.Request.RawUrl;
                Log.Info(contextErrMsg);
                throw new Exception(contextErrMsg);
            }

            blackList = this.FileReader.ReadList(this.Conf.BlackFile);
        }

        /// <summary>
        /// Main method - Method calls when client request the server
        /// </summary>
        public void ProcessRequest()
        {
            // Create a request with same data in navigator request
            HttpWebRequest request = this.GetRequest();
            
            try
            {
                // Send the request to the remote server and return the response
                HttpWebResponse response = this.GetResponse(request);
                
                if (response != null)
                {   // if the server return a response - manipulate it (add cookies, etc.) 
                    // and send it back to the client
                    byte[] responseData = this.GetResponseStreamBytes(response);
                    // transfer cookies to the client from the server...

                    // send the response back to the client
                    this.SendResponse(response, responseData);
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Did not complete handling request");
            }
        }


        /// <summary>
        /// Return address to communicate to the remote server
        /// </summary>
        public string RemoteUrl { get; private set; }

        /// <summary>
        /// Create a request the remote server
        /// </summary>
        /// <returns>Request to send to the server </returns>
        protected virtual HttpWebRequest GetRequest()
        {

            // Create a request to the server
            HttpWebRequest requestToServer = (HttpWebRequest)WebRequest.Create(this.RemoteUrl);

            // Set some options
            requestToServer.Method = this.Context.Request.HttpMethod;
            requestToServer.UserAgent = this.Context.Request.UserAgent;
            requestToServer.KeepAlive = requestToServer.KeepAlive;
            requestToServer.Proxy = null;
                   
            // TODO: add cookies
            requestToServer.CookieContainer = new CookieContainer();
            requestToServer.CookieContainer.Add(this.Context.Request.Url,this.Context.Request.Cookies);//לבדוק
            // TODO: add x-forward-for
            requestToServer.Headers.Add("x-forward-for:" + getIp(this.Context.Request.UserHostAddress));
            // TODO: add proxy version            
            requestToServer.Headers.Add("proxy-version:1.0");
            

            // For POST, write the post data extracted from the incoming request
            if (requestToServer.Method == "POST")
            {
                const int bufferSize = 1024;
                Stream clientStream = this.Context.Request.InputStream;
                byte[] clientPostData = new byte[bufferSize];
                requestToServer.ContentType = this.Context.Request.ContentType;
                int read = clientStream.Read(clientPostData, 0, bufferSize);
                StringBuilder data = new StringBuilder();
                Stream stream = requestToServer.GetRequestStream();
                //read data untile the end of the stream and write it to the rquest sended
                while (read > 0)
                {
                    string tempString = Encoding.UTF8.GetString(clientPostData, 0, clientPostData.Length);
                    data.Append(tempString);
                    stream.Write(clientPostData, 0, read);
                    read = clientStream.Read(clientPostData, 0, bufferSize);
                }
                stream.Close();

                //TODO: check for mails in the request (use data)
            }

            if (requestToServer.Method == "GET")
            {
              string url =  requestToServer.RequestUri.AbsoluteUri;
              
            }

            //check for mails in the request headers
            for (int i = 0; i < this.Context.Request.Headers.Count; i++)
            {
                //TODO: check for mails in the Headers[i]
            }
            return requestToServer;
        }

        /// <summary>
        /// Send the request to the remote server and return the response
        /// </summary>
        /// <param name="request">Request to send to the server </param>
        /// <returns>Response received from the remote server
        ///           or null if page not found </returns>
        private HttpWebResponse GetResponse(HttpWebRequest request)
        {
            HttpWebResponse response;

            try
            {
                response = (HttpWebResponse)request.GetResponse();
                


            }
            catch (WebException)
            {
                // Send 404 to client 
                this.Context.Response.StatusCode = 404;
                this.Context.Response.StatusDescription = "Page Not Found";
                //this._context.Response.("Page not found");
                this.Context.Response.Close();
                Console.WriteLine("404: Page Not Found");
                return null;
            }

            return response;
        }


        /// <summary>
        /// Return the response in bytes array format
        /// </summary>
        /// <param name="response">Response received
        ///             from the remote server </param>
        /// <returns>Response in bytes </returns>
        private byte[] GetResponseStreamBytes(HttpWebResponse response)
        {
            const int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            MemoryStream memoryStream = new MemoryStream();
            //get the response
            Stream responseStream = response.GetResponseStream();
            //read all the data from the response and save it in a memory stream
            if (responseStream != null)
            {
                int remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);

                while (remoteResponseCount > 0)
                {
                    memoryStream.Write(buffer, 0, remoteResponseCount);
                    remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);
                }
            }
            else
            {
                throw new Exception("Response is null");
            }

            //check for mails in the data and headers of the response
            StringBuilder sb = new StringBuilder();
            byte[] responseData = memoryStream.ToArray();
            string tempString = Encoding.UTF8.GetString(responseData, 0, responseData.Length);
            sb.Append(tempString);

            // TODO: email checking

            // close the open streams
            memoryStream.Close();
            responseStream.Close();
            memoryStream.Dispose();
            responseStream.Dispose();

            return responseData;
        }

        /// <summary> 
        ///  send the response back to the client
        /// </summary>
        /// <param name="response"> the response to send back </param>
        /// <param name="responseData"> the response data to send back </param>
        private void SendResponse(HttpWebResponse response, byte[] responseData)
        {
            // Send the response to client
            this.Context.Response.ContentEncoding = Encoding.UTF8;
            this.Context.Response.ContentType = response.ContentType;
            try
            {
                this.Context.Response.OutputStream.Write(responseData, 0, responseData.Length);
                this.Context.Response.Close();
            }
            catch (Exception)
            {
                Console.WriteLine("could not finish proxy request");
            }
        }



        /// <summary>
        /// getIp from address
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        private string getIp(string ip)
        {
            int index = ip.IndexOf(':');
            return ip.Substring(0, index);
        }
    }
}
